﻿using System.Linq;
using System.Reflection;
using System;
using System.Runtime.Serialization;

/**
* 
* Copyright (c) 2012 Anders Høst Kjærgaard | ahkj@itu.dk
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, 
* including without limitation the rights to use, copy, modify, merge, publish, distribute, 
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial 
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/


namespace Flatland.GeneticProgramming.CoreTypes {

    /// <summary>
    /// SetType denotes a member of the Funtion or Terminal set.
    /// </summary>
    public enum SetType {
        /// <summary>
        /// Terminal set.
        /// Variable at an array index
        /// </summary>
        ARRAY_INDEX,

        /// <summary>
        /// Terminal set.
        /// Single variable, e.g. 'X'
        /// </summary>
        VARIABLE,

        /// <summary>
        /// Terminal set.
        /// Internally used in the genetic code
        /// to add booleans to the GeneCommand
        /// </summary>
        CONSTANT,

        /// <summary>
        /// Terminal set.
        /// Parameterless method. 
        /// </summary>
        ZERO_ARITY_FUNCTION,

        /// <summary>
        /// Function set.
        /// Conditional if_then_else
        /// </summary>
        IF_THEN_ELSE,

        /// <summary>
        /// Function set.
        /// Parameterized method with non void return type
        /// </summary>
        FUNCTION,

        /// <summary>
        /// Function set.
        /// Sequence of functions or terminals. Used for GeneCommand
        /// </summary>
        SEQUENCE
    };

    /// <summary>
    /// Function is aggregated a MethodInfo and represents parameterized members of the function set
    /// </summary>
    [Serializable]
    public class Function : ICloneable {
        /// <summary>
        /// MethodInfo for for a SetType declared with a method
        /// </summary>
        public MethodInfo MethodInfo { get; set; }

        /// <summary>
        /// SetType, ie what type in the terminal or function set.
        /// </summary>
        public SetType SetType { get; set; }

        /// <summary>
        /// Base constructor for derived class Terminal.
        /// </summary>
        protected Function() { }

        /// <summary>
        /// Construct this function with memberinfo of the target method. 
        /// </summary>
        public Function(MethodInfo methodInfo) {
            MethodInfo = methodInfo;
            SetType = SetType.FUNCTION;
        }

        public Function(SetType setType) {
            SetType = setType;
        }

        public object Clone() {
            return new Function(MethodInfo) {
                SetType = SetType
            };
        }

        /// <summary>
        /// A Function can be equal to another Function if it is an equalivant CommandGenotype SetType.
        /// For all methods equality is defined by equal parameters and return type.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj) {
            if (obj == null) return false;
            if (obj.GetType() == typeof(Terminal))
                return EqualsTerminal((Terminal)obj);
            if (obj.GetType() == typeof(Function))
                return EqualsFunction((Function)obj);
            return false;
        }

        private bool EqualsFunction(Function function) {
            if (IsCommandSet())
                return function.IsCommandSet();
            if (function.IsCommandSet())
                return false;
            return true;
        }

        private bool EqualsTerminal(Terminal terminal) {
            if (IsCommandSet())
                return Terminal.ReturnType(terminal) == typeof(void);
            return MethodInfo.ReturnType == Terminal.ReturnType(terminal);
        }

        /// <summary>
        /// True if this function belongs to the command set
        /// </summary>
        /// <returns></returns>
        public bool IsCommandSet() {
            return SetType == SetType.IF_THEN_ELSE || SetType == SetType.SEQUENCE;
        }

        /// <summary>
        /// Returns true if two MethodInfo objects are interchangable.
        /// This is normally used to test if one Tree-Node can be exchanged for another. 
        /// Currently checks:
        /// Equal number of parameters
        /// Equal type of parameters (should be a redundant check, consider remove)
        /// Equal return type of method
        /// </summary>
        /// <param name="m1"></param>
        /// <param name="m2"></param>
        /// <returns></returns>
        private static bool MethodInfoCompare(MethodInfo m1, MethodInfo m2) {
            if (m1.GetParameters().Count() != m2.GetParameters().Count()) return false;
            for (int i = 0; i < m1.GetParameters().Count(); i++) {
                if (!(m1.GetParameters()[i].ParameterType == m2.GetParameters()[i].ParameterType)) return false;
            }
            if (m1.ReturnType != m2.ReturnType) return false;
            return true;
        }

        /// <summary>
        /// Deserialize constructor
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public Function(SerializationInfo info, StreamingContext context) {
            MethodInfo = (MethodInfo)info.GetValue("MethodInfo", typeof(MethodInfo));
        }

        /// <summary>
        /// Serialize.
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public void GetObjectData(SerializationInfo info, StreamingContext context) {
            info.AddValue("MethodInfo", MethodInfo);
        }
    }
}
